#include <stdio.h>
#include <glib.h>
#include "exec.h"
#include "blk.h"
#include "jit.h"
#include "intrp.h"
#include "helper.h"

void blk_dias (BasicBlock *blk) {
    target_size_t *insn;
    printf ("blk dias:\n");
    for (insn = blk->lc3_insn_blk.start; insn < (target_size_t*)blk->lc3_insn_blk.end; insn++) {
        printf ("%s\n", insn_name[get_insn_type (insn)]);
    }
}

BlockManager *
blkmgr_init (void *machine_status) {
    BlockManager *mgr = g_new0 (BlockManager, 1);
    mgr->blk_tab = g_hash_table_new (g_direct_hash, g_direct_equal);
    mgr->machine_status = machine_status;
    return mgr;
}

target_size_t
blk_exec_x86 (MachineStatus *ms, void *start, void *end) {
    target_size_t (*native_code) (target_size_t *reg, target_size_t *mem) = start;
    return native_code (ms->registers, ms->memory);
}

void
blk_free_x86 (BlockCtx *ctx) {
    // not implemented
}

void
blk_free_lc3 (BlockCtx *ctx) {
    // not implemented
}

int
blk_trans_x86 (BasicBlock *blk) {
    #define TB_SIZE 102400
    if (blk->status != BLK_LC3_LOADED) {
        return  -1;
    }
    void *exec_mem;
    exec_mem = jit_exec_malloc (TB_SIZE);
    exec_mem = jit_translate (blk, exec_mem, TB_SIZE);
    blk->x86_insn_blk.start = exec_mem;
    blk->x86_insn_blk.end = exec_mem + TB_SIZE;
    blk->x86_insn_blk.exec = blk_exec_x86;
    blk->x86_insn_blk.free = blk_free_x86;
    blk->status = BLK_X86_LOADED;
    return 0;
}

// main loop
target_size_t
blk_exec_internal (BasicBlock *blk) {
    target_size_t next_pc;
    MachineStatus *ms;
    for (;;) {
        ms = blk->machine_status;
        if (ms->hlt == TRUE)
            return 0;
        if (blk->status == BLK_EMPTY) {
            return  -1;
        } else if (blk->status == BLK_X86_LOADED) {
            //blk_dias (blk);
            //printf ("jmp in fire %p\n",  blk->x86_insn_blk.exec);
            next_pc =  blk->x86_insn_blk.exec (blk->machine_status, blk->x86_insn_blk.start, blk->x86_insn_blk.end);
            //printf ("return\n");
            //exit (0);
        } else if (blk->status == BLK_LC3_LOADED) {
            next_pc = blk->lc3_insn_blk.exec (blk->machine_status, blk->lc3_insn_blk.start, blk->lc3_insn_blk.end);
            if (blk->ex_cnt >= JIT_THRESHOLD) {
                // trigger jit
                blk_translate (blk);
            }
        } else {
            return -1;
        }
        blk = blkmgr_get_block (blk->blkmgr, next_pc);
    }
    return 0;
}


BasicBlock *
blk_init (BlockManager* mgr, guint addr) {
    if (mgr == NULL) return  NULL;
    BasicBlock * blk = g_new0 (BasicBlock, 1);
    blk->addr = addr;
    blk->blkmgr = mgr;
    blk->machine_status = mgr->machine_status;
    blk->blk_exec = blk_exec_internal;
    blk->blk_translate = blk_trans_x86;
    blk->status = BLK_EMPTY;
    return blk;
}

int
blk_destory (BlockManager* mgr, BasicBlock *blk) {
    if (blk->status == BLK_X86_LOADED) {
        blk->x86_insn_blk.free (&blk->x86_insn_blk);
        blk->lc3_insn_blk.free (&blk->lc3_insn_blk);
    } else if (blk->status == BLK_LC3_LOADED) {
        blk->lc3_insn_blk.free (&blk->lc3_insn_blk);
    }
    g_free (blk);
    return   0;
}

int
blk_load_lc3 (BasicBlock *blk,  target_size_t (*exec) (MachineStatus* ,void *, void *), void (*free_ctx) (BlockCtx *)) {
    if (blk->status != BLK_EMPTY)
        return -1;
    MachineStatus *ms = blk->machine_status;
    InsnType insn_type;
    unsigned short *insn_ptr;
    insn_ptr = ms->memory + blk->addr;
    // gint insn_cnt = 0;
    for (;;) {
        // insn_cnt++;
        if (insn_ptr - ms->memory >= MEMSZ) {
            //??? out of memory
            fprintf (stderr, "out of target memroy.\n");
            exit (-1);
            return -1;
            break;
        }
        insn_type = get_insn_type (insn_ptr);
        // printf ("loading :%p, %ld, %d\n", insn_ptr, insn_ptr - ms->memory, insn_type);
        insn_ptr++;
        if (insn_type == INSN_JMP || insn_type == INSN_BR ||
             insn_type == INSN_JSR || insn_type == INSN_JSRR || insn_type ==  INSN_TRAP) {
                //printf ("break\n");
                break;
        }
        // if (insn_cnt >= MAX_TB_INSN)
        //     break;
    }
    blk->lc3_insn_blk.start = ms->memory + blk->addr;
    blk->lc3_insn_blk.end = insn_ptr;
    blk->lc3_insn_blk.free = free_ctx;
    blk->lc3_insn_blk.exec = exec;
    blk->status = BLK_LC3_LOADED;
    return  0;
}

BasicBlock *
blkmgr_get_block (BlockManager* mgr, guint addr) {
    BasicBlock *blk;
    gulong key = (gulong) addr;
    if (g_hash_table_contains (mgr->blk_tab, (gpointer)key)) {
        blk = g_hash_table_lookup (mgr->blk_tab, (gpointer)key);
    } else {
        blk = blk_init (mgr, addr);
        blk_load_lc3 (blk, intrp_lc3_exec, blk_free_lc3);
        g_hash_table_insert (mgr->blk_tab, (gpointer)key, blk);
    }
    blk->ex_cnt ++;
    return  blk;
}